package com.ruoyi.wetest.stats;

import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import com.ruoyi.wetest.domain.WeTestTrans;
import com.ruoyi.wetest.domain.WetestStatLog;
import com.ruoyi.wetest.domain.WetestStressStatistics;
import com.ruoyi.wetest.domain.WetestStressTransMetric;
import com.ruoyi.wetest.mapper.WetestStatLogMapper;
import com.ruoyi.wetest.service.IWetestStatLogService;
import com.ruoyi.wetest.utils.WeTestConst;
import com.ruoyi.wetest.vo.Pair;
import com.ruoyi.wetest.vo.Triple;
import org.apache.lucene.util.RamUsageEstimator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import static com.ruoyi.wetest.utils.WeTestConst.ALL_CODE_NAME;

/**
 * @author Allen
 * @description TODO
 * @Date 2024/5/6 15:25
 */
public class MessageMetric {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final String testId;

    private final AtomicInteger number;

    public final CodeStat allCodeStat;

    private final ThreadPoolTaskExecutor executor;

    private final Map<String, CodeStat> codeStatsMap = new ConcurrentHashMap<>();

    private final Map<String, WetestStatLog> codeStatsLogMap = new ConcurrentHashMap<>();
    private final int codeStatsLog = 5000;

    private volatile boolean running;

    private final long startTime;
    private long endTime;//
    private final IWetestStatLogService iWetestStatLogService;
    private ConcurrentHashMap<Integer, Pair<Integer, Integer>> statisticsHashMap;


    public MessageMetric(String testId, ThreadPoolTaskExecutor executor, IWetestStatLogService iWetestStatLogService) {
        this.testId = testId;
        this.executor = executor;
        this.iWetestStatLogService = iWetestStatLogService;
        this.number = new AtomicInteger();
        this.running = true;
        this.startTime = System.currentTimeMillis();
        this.allCodeStat = new CodeStat(number.incrementAndGet(), ALL_CODE_NAME);
        codeStatsMap.put(ALL_CODE_NAME, this.allCodeStat);
        this.statisticsHashMap = new ConcurrentHashMap<>();

    }

    public void addReceivedMsgStat(List<WeTestTrans> trans) {
        executor.execute(() -> {
            for (WeTestTrans tran : trans) {
                addReceivedMsgStat(tran);
            }
        });
    }

    private void addReceivedMsgStat(WeTestTrans req) {
        CodeStat cs = this.codeStatsMap.get(req.getTrans_name());
        if (cs == null) {
            synchronized (this) {
                cs = this.codeStatsMap.get(req.getTrans_name());
                if (cs == null) {
                    cs = new CodeStat(number.incrementAndGet(), req.getTrans_name());
                    this.codeStatsMap.put(cs.getTransName(), cs);
                }
            }
        }
        SingleCodeStat ss = new SingleCodeStat(req.getTrans_name(), false, req.getTime_cost(), req.getTrans_result());
        handleStat(ss, cs);
        handleStat(ss, this.allCodeStat);
        handleStatLog(req);
    }

    private void handleStatLog(WeTestTrans req) {
        handleStatLog(req.getTrans_name(), req.getTimestamps(), 1, WeTestConst.STAT_TRANS_SUB_TYPE, null);
        handleStatLog(WeTestConst.STAT_TPS, req.getTimestamps(), 1, WeTestConst.STAT_ALL_SUB_TYPE, null);
    }

    private void handleStatLog(String type, long timestamps, long v, String sy, String m) {
        synchronized (this) {
            String key = type + "_" + timestamps;
            WetestStatLog sl = codeStatsLogMap.get(key);
            if (sl == null) {
                sl = new WetestStatLog();
                sl.setTimestamps(timestamps);
                sl.setValue(v);
                sl.setSubType(sy);
                sl.setType(type);
                sl.setTestId(testId);
                if (m != null) sl.setRemark(m);

            } else {
                sl.setValue(sl.getValue() + v);
            }
            codeStatsLogMap.put(key, sl);
            saveStatLog(false);
        }
    }


    private void saveStatLog(boolean flag) {
        if (this.codeStatsLogMap.size() >= codeStatsLog || flag) {
            List<WetestStatLog> ls = new ArrayList<>(codeStatsLogMap.values());
            if (!ls.isEmpty()) {
                iWetestStatLogService.saveBatchWetestStatLog(ls);
            }
            codeStatsLogMap.clear();
        }
    }


    private void handleStat(SingleCodeStat ss, CodeStat cs) {
        if (ss == null || cs == null) {
            return;
        }
        if (!ss.isTransmit()) {
            cs.getOneWayRx().update(ss.getStatus());
        }
        if (ss.getTime() > 0) {
            cs.updateTimer(ss.getTime(), TimeUnit.NANOSECONDS);
        }
    }

    public void fixedTimeSnapshot() {
        double durationFactor = TimeUnit.MILLISECONDS.toNanos(1);
        double rateFactor = TimeUnit.SECONDS.toSeconds(1);
        long timestamps = System.currentTimeMillis() / 1000;
        List<WetestStatLog> ls = new ArrayList<>();
        for (CodeStat stat : this.codeStatsMap.values()) {
            if (Objects.equals(stat.getTransName(), ALL_CODE_NAME)) continue;
            Timer rxtx2 = stat.getRxtx2();
            Snapshot snapshot = rxtx2.getSnapshot();
            if (snapshot == null) continue;
            String snapshotByStr = MessageSnapshot.HistogramSnapshot.getHistogramSnapshotByStr(snapshot, durationFactor);
            WetestStatLog sl = new WetestStatLog();
            sl.setTimestamps(timestamps);
            sl.setValue(0L);
            sl.setSubType(WeTestConst.STAT_COST_TIME_SUB_TYPE);
            sl.setType(stat.getTransName() + "_c");
            sl.setTestId(testId);
            sl.setRemark(snapshotByStr);
            ls.add(sl);
          /*  Double v = MessageSnapshot.convertRate(rxtx2.getMeanRate(), rateFactor);
            sl= new WetestStatLog();
            sl.setTimestamps(timestamps);
            sl.setValue(v.longValue());
            sl.setSubType(WeTestConst.STAT_TRANS_SUB_TYPE);
            sl.setType(stat.getTransName());
            sl.setTestId(testId);
            ls.add(sl);*/
        }
        if (!ls.isEmpty()) {
            iWetestStatLogService.saveBatchWetestStatLog(ls);
            logger.info("fixedTimeSnapshot ls:{}", ls.size());
        }
    }

    public void stop() {
        running = false;
        endTime = System.currentTimeMillis();
        saveStatLog0();
    }

    public void saveStatLog0(){
        synchronized (this) {
            saveStatLog(true);
        }
    }

    public Map<String, WetestStressTransMetric> getSnapshotList() {
        Map<String, WetestStressTransMetric> ls = new HashMap<>();
        MessageSnapshot snapshot = getSnapshot();
        Date ctime = new Date();
        for (MessageSnapshot.CodeStatSnapshot css : snapshot.getCodeStatSnapshots()) {
            MessageSnapshot.TimerSnapshot rxtx = css.rxtx;
            MessageSnapshot.HistogramSnapshot hs = rxtx.histogramSnapshot;
            MessageSnapshot.OneWaySnapshot ow = css.oneWayRx;
            WetestStressTransMetric m = new WetestStressTransMetric(testId, css.number.longValue(),
                    css.transName,
                    ow.packs,
                    ow.succ,
                    ow.fail,
                    ow.error,
                    ow.timeout,
                    BigDecimal.valueOf(ow.succRate),
                    BigDecimal.valueOf(rxtx.meanRate).setScale(2, RoundingMode.HALF_DOWN),
                    hs.max.longValue(),
                    hs.min.longValue(),
                    BigDecimal.valueOf(hs.mean).setScale(2, RoundingMode.HALF_DOWN),
                    hs.p50.longValue(),
                    hs.p75.longValue(),
                    hs.p90.longValue(),
                    hs.p95.longValue(),
                    hs.p99.longValue(),
                    hs.p999.longValue(),
                    ctime);
            ls.put(css.transName, m);
        }
        return ls;
    }

    public CodeStat getAllCodeStat() {
        return allCodeStat;
    }

    public MessageSnapshot getSnapshot() {
        return getSnapshot(TimeUnit.SECONDS, TimeUnit.MILLISECONDS);
    }

    public MessageSnapshot getSnapshot(TimeUnit rateUnit,
                                       TimeUnit durationUnit) {
        List<MessageSnapshot.CodeStatSnapshot> snapshots = snapshot(rateUnit, durationUnit, this.codeStatsMap.values());
        return new MessageSnapshot(startTime, System.currentTimeMillis(), snapshots);
    }


    public void statistics(WetestStressStatistics statistics) {
        Pair<Integer, Integer> pair = statisticsHashMap.computeIfAbsent(statistics.getInstanceId(), k -> new Pair<>(0, 0));
        if (pair.first < statistics.getRobotTotalNum()) {
            pair.first = statistics.getRobotTotalNum();
        }
        pair.second += statistics.getRobotOnlineNum();
        long l = statistics.getcTime() / 1000;
        handleStatLog(WeTestConst.STAT_ONLINE, l, statistics.getRobotOnlineNum(), WeTestConst.STAT_ALL_SUB_TYPE, null);
        handleStatLog(WeTestConst.STAT_RT, l, statistics.getRecvPkgTotalNum(), WeTestConst.STAT_ALL_SUB_TYPE, null);
        handleStatLog(WeTestConst.STAT_TX, l, statistics.getSendPkgTotalNum(), WeTestConst.STAT_ALL_SUB_TYPE, null);
    }


    public Pair<Integer, Integer> getPcu() {
        int tpcu = 0;
        int opcu = 0;
        for (Pair<Integer, Integer> pair : this.statisticsHashMap.values()) {
            tpcu += pair.first;
            opcu += pair.second;
        }
        return new Pair<>(tpcu, opcu);
    }

    private List<MessageSnapshot.CodeStatSnapshot> snapshot(TimeUnit rateUnit,
                                                            TimeUnit durationUnit,
                                                            Collection<CodeStat> codeStats) {

        double rateFactor = rateUnit.toSeconds(1);
        double durationFactor = durationUnit.toNanos(1);
        List<MessageSnapshot.CodeStatSnapshot> snapshots = new ArrayList<>(codeStats.size());
        //统计Histogram Reservoir 总内存占用
        long sumMemSize = 0;
        for (CodeStat cs : codeStats) {
            sumMemSize += cs.getUniform2Reservoir().getMemSize();
            MessageSnapshot.CodeStatSnapshot snapshot = new MessageSnapshot.CodeStatSnapshot(cs.getTransName(), cs.getNumber(),
                    new MessageSnapshot.TimerSnapshot(cs.getRxtx(), rateFactor, durationFactor),
                    new MessageSnapshot.OneWaySnapshot(cs.getOneWayRx()));
            snapshots.add(snapshot);
        }
        if (sumMemSize > 0) {
            logger.info("snapshot cs:{},total histogram mem:{}", codeStats.size(), RamUsageEstimator.humanReadableUnits(sumMemSize));
        }
        return snapshots;
    }


    public int getCodeStatsMapSize() {
        return codeStatsMap.size();
    }

    public boolean isRunning() {
        return running;
    }
}
